/**
 * @file   PacketParser.cpp
 * @author Djuro Drljaca (djurodrljaca@gmail.com)
 * @date   2014-04-21
 * @brief  DON packet parser object.
 *
 * Copyright 2014  Djuro Drljaca (djurodrljaca@gmail.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <DON/Core/Packet/PacketParser.h>
#include <DON/Core/Packet/PacketHeader.h>
#include <DON/Core/Packet/PacketDefines.h>
#include <DON/Core/Crc16/Crc16.h>
#include <QtCore/QByteArray>
#include <QtCore/QList>

#define PACKET_STX    (DON_CORE_PACKET_STX_VALUE)
#define PACKET_ETX    (DON_CORE_PACKET_ETX_VALUE)

#define PACKET_HEADER_SIZE    (13)

using namespace DON::Core::Packet;

enum ParserState
{
    ParserState_Init,
    ParserState_WaitForStx,
    ParserState_WaitForHeader,
    ParserState_WaitForPayload,
    ParserState_WaitForEtx
};

class PacketParser::Private
{
public:
    Private()
        : buffer(QByteArray()),
          bufferIndex(0),
          state(ParserState_Init),
          packetHeader(PacketHeader()),
          packetPayload(QByteArray()),
          packetList(QList<GenericPacket>())
    { }

    /**
     * @brief Process state: Init
     */
    void processStateInit();

    /**
     * @brief Process state: Wait for STX
     */
    void processStateWaitForStx(bool *waitForMoreData);

    /**
     * @brief Process state: Wait for header
     */
    void processStateWaitForHeader(bool *waitForMoreData);

    /**
     * @brief Process state: Wait for payload
     */
    void processStateWaitForPayload(bool *waitForMoreData);

    /**
     * @brief Process state: Wait for ETX
     */
    void processStateWaitForEtx(bool *waitForMoreData);

    QByteArray buffer;
    int bufferIndex;
    ParserState state;
    PacketHeader packetHeader;
    QByteArray packetPayload;
    QList<GenericPacket> packetList;
};

PacketParser::PacketParser()
    : d(new Private())
{
}

PacketParser::~PacketParser()
{
    delete d;
}

void PacketParser::addData(const QByteArray &data)
{
    d->buffer.append(data);
}

void PacketParser::parseData()
{
    bool waitForMoreData = false;

    // Parse data until more data is needed
    while (waitForMoreData == false)
    {
        // Process current state
        switch (d->state)
        {
            case ParserState_WaitForStx:
            {
                d->processStateWaitForStx(&waitForMoreData);
                break;
            }

            case ParserState_WaitForHeader:
            {
                d->processStateWaitForHeader(&waitForMoreData);
                break;
            }

            case ParserState_WaitForPayload:
            {
                d->processStateWaitForPayload(&waitForMoreData);
                break;
            }

            case ParserState_WaitForEtx:
            {
                d->processStateWaitForEtx(&waitForMoreData);
                break;
            }

            case ParserState_Init:
            default:
            {
                d->processStateInit();
                waitForMoreData = false;
                break;
            }
        }
    }
}

bool PacketParser::packetsAvailable() const
{
    bool available = false;

    if (d->packetList.isEmpty() == false)
    {
        available = true;
    }

    return available;
}

GenericPacket PacketParser::takePacket(bool *ok)
{
    bool success = false;
    GenericPacket packet;

    if (d->packetList.isEmpty() == false)
    {
        packet = d->packetList.takeFirst();
        success = true;
    }

    if (ok != NULL)
    {
        *ok = success;
    }

    return packet;
}

void PacketParser::Private::processStateInit()
{
    buffer.clear();
    bufferIndex = 0;
    packetHeader = PacketHeader();
    packetPayload.clear();
    packetList.clear();

    state = ParserState_WaitForStx;
}

void PacketParser::Private::processStateWaitForStx(bool *waitForMoreData)
{
    bool stxFound = false;

    // Search for STX only if there is data insde the buffer
    if (buffer.isEmpty() == false)
    {
        // Try to find STX
        const int index = buffer.indexOf(static_cast<char>(PACKET_STX));

        if (index == 0)
        {
            // Found at the start of the buffer
            stxFound = true;
        }
        else if (index > 0)
        {
            // Found after the start of the buffer
            // Remove garbage bytes
            buffer.remove(0, index);
            stxFound = true;
        }
        else
        {
            // STX was not found
            // Remove garbage bytes
            buffer.clear();
            stxFound = false;
        }
    }

    // Check if STX was found
    if (stxFound)
    {
        // Wait for packet header
        *waitForMoreData = false;
        bufferIndex = 1;
        state = ParserState_WaitForHeader;
    }
    else
    {
        // Wait for more data
        *waitForMoreData = true;
    }
}

void PacketParser::Private::processStateWaitForHeader(bool *waitForMoreData)
{
    bool error = false;
    bool headerFound = false;

    // Search for packet header only if there is data insde the buffer
    if (buffer.size() >= (bufferIndex + PACKET_HEADER_SIZE))
    {
        // Parse packet header
        PacketHeader header;

        bool success = header.fromBinary(buffer, bufferIndex);

        if (success)
        {
            // Valid packet header found
            packetHeader = header;
            headerFound = true;
        }
        else
        {
            // Error occured
            error = true;
        }
    }

    // Check if an error occured or if a packet header was found
    if (error)
    {
        // An error occured while waiting for a packet header
        // Remove STX and wait for the next STX
        *waitForMoreData = false;
        buffer.remove(0, 1);
        bufferIndex = 0;
        state = ParserState_WaitForStx;
    }
    else if (headerFound)
    {
        // Packet header was found
        ParserState nextState;

        // Check if packet payload is expected
        if (packetHeader.getPayloadLength() > 0)
        {
            // Packet payload is expected
            // Wait for packet payload
            nextState = ParserState_WaitForPayload;
        }
        else
        {
            // Packet payload is not expected
            // Clear packet payload and wait for ETX
            packetPayload.clear();
            nextState = ParserState_WaitForEtx;
        }

        *waitForMoreData = false;
        bufferIndex += PACKET_HEADER_SIZE;
        state = nextState;
    }
    else
    {
        // Wait for more data
        *waitForMoreData = true;
    }
}

void PacketParser::Private::processStateWaitForPayload(bool *waitForMoreData)
{
    bool error = false;
    bool payloadFound = false;

    // Search for packet payload only if there is data insde the buffer
    const int payloadLength = static_cast<int>(packetHeader.getPayloadLength());

    if (buffer.size() >= (bufferIndex + payloadLength))
    {
        bool success = false;

        // Calculate packet payload checksum
        const quint16 payloadChecksum =
                Crc16::calculate(buffer, bufferIndex, payloadLength, &success);

        if (success)
        {
            if (payloadChecksum == packetHeader.getPayloadChecksum())
            {
                // Packet payload found
                payloadFound = true;
            }
            else
            {
                // Error, invalid checksum
                error = true;
            }
        }
        else
        {
            // Error, failed to calculate checksum
            error = true;
        }
    }

    // Check if an error occured or if a packet payload was found
    if (error)
    {
        // An error occured while waiting for a packet payload
        // Remove STX and wait for the next STX
        *waitForMoreData = false;
        buffer.remove(0, 1);
        bufferIndex = 0;
        state = ParserState_WaitForStx;
    }
    else if (payloadFound)
    {
        // Packet payload was found
        // Wait for ETX
        *waitForMoreData = false;
        packetPayload = buffer.mid(bufferIndex, payloadLength);
        bufferIndex += PACKET_HEADER_SIZE;
        state = ParserState_WaitForEtx;
    }
    else
    {
        // Wait for more data
        *waitForMoreData = true;
    }
}

void PacketParser::Private::processStateWaitForEtx(bool *waitForMoreData)
{
    bool error = false;
    bool etxFound = false;

    // Search for ETX only if there is data insde the buffer
    if (buffer.size() > bufferIndex)
    {
        // Try to find ETX
        const char value = buffer.at(bufferIndex);

        if (value == static_cast<char>(PACKET_ETX))
        {
            // ETX found
            etxFound = true;
        }
        else
        {
            // Error, invalid value
            error = true;
        }
    }

    // Check if an error occured or if a packet payload was found
    if (error)
    {
        // An error occured while waiting for ETX
        // Remove STX and wait for the next STX
        *waitForMoreData = false;
        buffer.remove(0, 1);
        bufferIndex = 0;
        state = ParserState_WaitForStx;
    }
    else if (etxFound)
    {
        // ETX was found
        // Create packet
        GenericPacket packet(packetHeader.getSource(),
                             packetHeader.getDestination(),
                             packetHeader.getPacketType(),
                             packetHeader.getSequence(),
                             packetPayload);

        // Save Packet
        packetList.append(packet);

        // Wait for STX
        *waitForMoreData = false;
        buffer.remove(0, bufferIndex + 1);
        bufferIndex = 0;
        state = ParserState_WaitForStx;
    }
    else
    {
        // Wait for more data
        *waitForMoreData = true;
    }
}
